// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#pragma once

#include <boost/foreach.hpp>

#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/json_writer.h"

namespace coin2::exchange::bybit::api_order_v3 {

class BybitSpotBaseAuth {
 public:
  virtual ~BybitSpotBaseAuth() = default;

  static std::string GetSignature(
      const std::string& timestamp,
      const std::string& api_key,
      const std::string& query,
      const std::string& body,
      const std::string& secret_key) {
    if (secret_key.empty()) {
      throw std::runtime_error("[Bybit] API key and secret key are required");
    }
    CHECK(query.empty() || body.empty()) << "query and body both not empty!";

    std::string param_str = timestamp + api_key + query + body;

    return ApiSignatureUtil::GetSignatureByHexHmacSHA256(param_str, secret_key);
  }
};

class BybitSpotWsAuth : public BybitSpotBaseAuth {
 public:
  explicit BybitSpotWsAuth(const std::string& key_filepath)
      : key_(AuthKey::FromFile(key_filepath)) {}

  std::string CreateSignature() const {
    std::list<std::string> out;
    std::string timestamp = std::to_string(GetCurrentTimestamp() / 1000000 + 10000);
    std::string sign = ApiSignatureUtil::GetSignatureByHexHmacSHA256(
        "GET/realtime" + timestamp,
        key_.secret_key());
    out.emplace_back(key_.access_key());
    out.emplace_back(timestamp);
    out.emplace_back(sign);

    JsonWriter writer;
    writer.PutString("op", "auth");
    writer.PutArray("args", out);
    return writer.ToJsonString();
  }

 protected:
  AuthKey key_;
};

class BybitSpotRestAuth : public BybitSpotBaseAuth, public BaseRestAuth {
 public:
  explicit BybitSpotRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {}

  explicit BybitSpotRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)),
        host_(host),
        port_(std::to_string(port)),
        bind_ip_(bind_ip) {}

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string timestamp = std::to_string(GetCurrentTimestamp() / 1000000);
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    BuildHeader(timestamp, key_.access_key(), key_.secret_key(), query_string, post_body);

    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewGet(host_, port_, bind_ip_, target, header_);
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    CHECK(query_string.empty());
    std::string timestamp = std::to_string(GetCurrentTimestamp() / 1000000);
    std::string post_body = builder->ComputePostBody();
    BuildHeader(timestamp, key_.access_key(), key_.secret_key(), query_string, post_body);

    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewPost(host_, port_, bind_ip_, target, header_, post_body);
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string timestamp = std::to_string(GetCurrentTimestamp() / 1000000);
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());
    BuildHeader(timestamp, key_.access_key(), key_.secret_key(), query_string, post_body);

    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    return AsioHttpContext::NewDelete(host_, port_, bind_ip_, target, header_, "");
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

 private:
  void BuildHeader(
      const std::string& timestamp,
      const std::string& access_key,
      const std::string& secret_key,
      const std::string& query,
      const std::string& body) {
    header_.clear();
    std::string sign = GetSignature(timestamp, access_key, query, body, secret_key);
    header_.push_back(std::make_pair("X-BAPI-SIGN-TYPE", "2"));
    header_.push_back(std::make_pair("X-BAPI-SIGN", sign));
    header_.push_back(std::make_pair("X-BAPI-API-KEY", access_key));
    header_.push_back(std::make_pair("X-BAPI-TIMESTAMP", timestamp));
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
  std::vector<std::pair<std::string, std::string>> header_;
};

}  // namespace coin2::exchange::bybit::api_order_v3
